
# 开发一个仿照 Flutter 的UI库，这里仅提供以下教学网站中提供的控件，你可以提供其它控件。
# 参考 https://flutter.cn/docs/get-started/flutter-for/web-devs

def maketag(tag, attrs=None, style=None):
	elt = javascript.document.createElement(tag)
	if attrs is not None:
		for k,v in attrs.items():
			elt.setAttribute(k,v)
	if style is not None:
		style = ';'.join([f'{k}:{v}' for k,v in style.items()])
		elt.setAttribute('style',style)
	return elt

def add_class(elt, *classes):
	classes0 = elt.getAttribute('class').data()
	if classes0 is None:
		classes0 = set()
	else:
		classes0 = set(classes0.split())
	for c in classes:
		classes0.add(c)
	elt.setAttribute('class', ' '.join(classes0))

def remove_class(elt, *classes):
	classes0 = elt.getAttribute('class').data()
	if classes0 is None:
		classes0 = set()
	else:
		classes0 = set(classes0.split())
	for c in classes:
		classes0.remove(c)
	elt.setAttribute('class', ' '.join(classes0))

from . import flutter_colors
Colors = flutter_colors.Colors

class EdgeInsets:
	def __init__(self, sizes):
		assert isinstance(sizes, (tuple,list))
		self.sizes = sizes
	def __repr__(self):
		return ' '.join([f'{s}px' if isinstance(s, int) else s for s in self.sizes]) 
	def all(sizes):
		return EdgeInsets([sizes])
	def only(top=0, right=0, bottom=0, left=0):
		return EdgeInsets([top, right, bottom, left])
	def symmetric(horizontal=0, vertical=0):
		return EdgeInsets([vertical, horizontal, vertical, horizontal])

class BorderRadius:
	def __init__(self, sizes):
		assert isinstance(sizes, (tuple,list))
		self.sizes = sizes
	def __repr__(self):
		return ' '.join([f'{s}px' if isinstance(s, int) else s for s in self.sizes]) 
	def all(sizes):
		return BorderRadius([sizes])
	def only(top=0, right=0, bottom=0, left=0):
		return BorderRadius([top, right, bottom, left])

class LinearGradient:
	def __init__(self, degree, colors):
		assert isinstance(degree, (int, str))
		assert isinstance(colors, (tuple,list)) and (len(colors)==2)
		for c in colors:
			assert isinstance(c, str)
		self.degree = degree
		self.colors = colors
	def __repr__(self):
		degree = f'{self.degree}deg' if isinstance(self.degree, int) else self.degree
		return f'linear-gradient({degree}, {self.colors[0]}, {self.colors[1]})'

class Offset:
	def __init__(self, x, y):
		assert isinstance(x, int)
		assert isinstance(y, int)
		self.x = x
		self.y = y

class BoxShadow:
	def __init__(self, color, offset, blurRadius):
		assert isinstance(color, str)
		assert isinstance(offset, Offset)
		assert isinstance(blurRadius, int)
		self.color = color
		self.offset = offset
		self.blurRadius = blurRadius
	def __repr__(self):
		x = self.offset.x
		y = self.offset.y
		blurRadius = self.blurRadius
		if isinstance(x, int): x = f'{x}px'
		if isinstance(y, int): y = f'{y}px'
		if isinstance(blurRadius, int): blurRadius = f'{blurRadius}px'
		return f'{x} {y} {blurRadius} {self.color}'

class BoxShape:
	circle = 'circle'

class Container:
	def __init__(self, child, width=None, height=None, color=None, borderRadius=None,
				gradient=None, padding=None, margin=None, boxShadow=None, shape=None):
		assert hasattr(child, 'render') and callable(child.render)
		assert (width is None) or (isinstance(width, (int, str)))
		assert (height is None) or (isinstance(height, (int, str)))
		assert (color is None) or (isinstance(color, str))
		assert (borderRadius is None) or (isinstance(borderRadius, BorderRadius))
		assert (gradient is None) or (isinstance(gradient, LinearGradient))
		assert (padding is None) or (isinstance(padding, EdgeInsets))
		assert (margin is None) or (isinstance(margin, EdgeInsets))
		if boxShadow is not None:
			assert isinstance(boxShadow, (tuple,list))
			for b in boxShadow:
				assert isinstance(b, BoxShadow)
		assert (shape is None) or (isinstance(shape, str))
		self.child = child
		self.width = width
		self.height = height
		self.color = color
		self.borderRadius = borderRadius
		self.gradient = gradient
		self.padding = padding
		self.margin = margin
		self.boxShadow = boxShadow
		self.shape = shape
	def render(self):
		style = {}
		if self.width is not None:
			style['width'] = f'{self.width}px' if isinstance(self.width, int) else self.width
		if self.height is not None:
			style['height'] = f'{self.height}px' if isinstance(self.height, int) else self.height
		if self.color is not None:
			style['background-color'] = self.color
		if self.padding is not None:
			style['padding'] = str(self.padding)
		if self.margin is not None:
			style['margin'] = str(self.margin)
		if self.borderRadius is not None:
			style['border-radius'] = str(self.borderRadius)
		if self.gradient is not None:
			style['background'] = str(self.gradient)
		if self.boxShadow is not None:
			style['box-shadow'] = ', '.join([str(b) for b in self.boxShadow])
		if self.shape is not None:
			if self.shape=='circle':
				style['border-radius'] = '50%'
		if hasattr(self.child, 'textAlign'):
			if self.child.textAlign is not None:
				style['text-align'] = self.child.textAlign
		e = maketag('div', style=style)
		e.appendChild(self.child.render())
		return e

class FontWeight:
	w100 = '100'
	w200 = '200'
	w300 = '300'
	w400 = '400'
	w500 = '500'
	w600 = '600'
	w700 = '700'
	w800 = '800'
	w900 = '900'

class FontStyle:
	italic = 'italic'

class TextStyle:
	def __init__(self, fontSize=None, fontWeight=None, fontFamily=None, 
				letterSpacing=None, fontStyle=None):
		assert (fontSize is None) or isinstance(fontSize, (int, str))
		assert (fontWeight is None) or isinstance(fontWeight, str)
		assert (fontFamily is None) or isinstance(fontFamily, str)
		assert (letterSpacing is None) or (isinstance(letterSpacing, (int,str)))
		assert (fontStyle is None) or (isinstance(fontStyle, str))
		self.fontSize = fontSize
		self.fontWeight = fontWeight
		self.fontFamily = fontFamily
		self.letterSpacing = letterSpacing
		self.fontStyle = fontStyle
	def __repr__(self):
		style = []
		if self.fontWeight is not None:
			style.append(self.fontWeight)
		if self.fontSize is not None:
			style.append(f'{self.fontSize}px' if isinstance(self.fontSize, int) else self.fontSize)
		if self.fontFamily is not None:
			style.append(self.fontFamily)
		return ' '.join(style)

class TextAlign:
	center = 'center'
	left = 'left'
	right = 'right'

class TextOverflow:
	clip = 'clip'
	ellipsis = 'ellipsis'

class Text:
	def __init__(self, text, color=None, textAlign=None, style=None,
				overflow=TextOverflow.clip, maxLines=None):
		assert (textAlign is None) or (isinstance(textAlign, str))
		assert (style is None) or (isinstance(style, TextStyle))
		assert (color is None) or (isinstance(color, str))
		assert isinstance(overflow, str)
		if maxLines is not None:
			assert isinstance(maxLines, int)
			assert maxLines>=1
		self.text = text
		self.color = color
		self.textAlign = textAlign
		self.style = style
		self.overflow = overflow
		self.maxLines = maxLines
	def render(self):
		style = {}
		if self.style is not None:
			style['font'] = str(self.style)
			if self.style.letterSpacing is not None:
				letterSpacing = self.style.letterSpacing
				if isinstance(letterSpacing, int):
					letterSpacing = f'{letterSpacing}px'
				style['letter-spacing'] = letterSpacing
			if self.style.fontStyle is not None:
				style['font-style'] = self.style.fontStyle
		if self.color is not None:
			style['color'] = self.color
		if self.maxLines is not None:
			style['overflow'] = 'hidden'
			style['text-overflow'] = self.overflow
			style['display'] = '-webkit-box'
			style['-webkit-line-clamp'] = self.maxLines
			style['-webkit-box-orient'] = 'vertical'
		e = maketag('div', style=style)
		p = javascript.document.createTextNode(self.text)
		e.appendChild(p)
		return e

class RichText:
	def __init__(self, text):
		assert isinstance(text, TextSpan)
		self.text = text
	def render(self):
		return self.text.render()

class TextSpan:
	def __init__(self, text=None, color=None, style=None, children=None):
		assert (text is not None) or (children is not None)
		assert (color is None) or (isinstance(color, str))
		assert (style is None) or isinstance(style, TextStyle)
		if text is not None:
			assert isinstance(text, str)
		if children is not None:
			assert isinstance(children, (tuple,list))
			for c in children:
				assert isinstance(c, TextSpan)
		self.text = text
		self.color = color
		self.style = style
		self.children = children
	def render(self):
		style = {}
		if self.style is not None:
			style['font'] = str(self.style)
			if self.style.letterSpacing is not None:
				letterSpacing = self.style.letterSpacing
				if isinstance(letterSpacing, int):
					letterSpacing = f'{letterSpacing}px'
				style['letter-spacing'] = letterSpacing
			if self.style.fontStyle is not None:
				style['font-style'] = self.style.fontStyle
		if self.color is not None:
			style['color'] = self.color
		e = maketag('span', style=style)
		if self.text is not None:
			p = javascript.document.createTextNode(self.text)
			e.appendChild(p)
		elif self.children is not None:
			for c in self.children:
				e.appendChild(c.render())
		return e

class Center:
	def __init__(self, child):
		assert hasattr(child, 'render') and callable(child.render)
		self.child = child
	def render(self):
		e = maketag('div', style={'display': 'flex', 'align-items': 'center', 
								'justify-content': 'center', 'width': '100%', 'height': '100%'})
		e.appendChild(self.child.render())
		return e

class Stack:
	def __init__(self, children):
		assert isinstance(children, (tuple,list))
		for c in children:
			assert isinstance(c, Positioned)
		self.children = children
	def render(self):
		e = maketag('div',style={'position':'relative'})
		for c in self.children:
			e.appendChild(c.render())
		return e

class Positioned:
	def __init__(self, child, left, top):
		assert hasattr(child, 'render') and callable(child.render)
		assert isinstance(left, (int, str))
		assert isinstance(top, (int, str))
		self.child = child
		self.left = left
		self.top = top
	def render(self):
		top = f'{self.top}px' if isinstance(self.top, int) else self.top
		left = f'{self.left}px' if isinstance(self.left, int) else self.left
		e = maketag('div', style={'position':'absolute','top':top,'left':left})
		e.appendChild(self.child.render())
		return e

class Transform:
	def __init__(self, child, rotate=None, scale=None):
		assert hasattr(child, 'render') and callable(child.render)
		assert (rotate is None) or isinstance(rotate, (int, str))
		assert (scale is None) or isinstance(scale, float)
		self.child = child
		self.rotate = rotate
		self.scale = scale
	def render(self):
		transform = []
		if self.rotate is not None:
			rotate = f'{self.rotate}deg' if isinstance(self.rotate, int) else self.rotate
			transform.append(f'rotate({rotate})')
		if self.scale is not None:
			transform.append(f'scale({self.scale})')
		e = maketag('div')
		if len(transform)>0:
			e.style.transform = ' '.join(transform)
		e.appendChild(self.child.render())
		return e


#https://www.cnblogs.com/hellocd/p/10443237.html

class Axis: # flex-direction
	horizontal = 'row'
	vertical = 'column'

class MainAxisAlignment: # justify-content
	center = 'center'
	end = 'flex-end'
	spaceAround = 'space-around'
	spaceBetween = 'space-between'
	spaceEvenly = 'space-evenly'
	start = 'flex-start'

class CrossAxisAlignment: # align-items
	baseline = 'baseline'
	center = 'center'
	end = 'end'
	start = 'start'
	stretch = 'stretch'

class MainAxisSize:
	max = 'max'
	min = 'min'

class Flex:
	def __init__(self, children, direction, 
			mainAxisAlignment=MainAxisAlignment.start, 
			crossAxisAlignment=CrossAxisAlignment.center, 
			mainAxisSize=MainAxisSize.min):
		assert isinstance(children, (tuple,list))
		for child in children:
			assert hasattr(child, 'render') and callable(child.render), type(child).__name__
		self.children = children
		assert direction in (Axis.horizontal, Axis.vertical)
		assert mainAxisAlignment in (MainAxisAlignment.center,
									MainAxisAlignment.end,
									MainAxisAlignment.spaceAround,
									MainAxisAlignment.spaceBetween,
									MainAxisAlignment.spaceEvenly,
									MainAxisAlignment.start)
		assert crossAxisAlignment in (CrossAxisAlignment.baseline,
									CrossAxisAlignment.center,
									CrossAxisAlignment.end,
									CrossAxisAlignment.start,
									CrossAxisAlignment.stretch)
		assert mainAxisSize in (MainAxisSize.max,
									MainAxisSize.min)
		self.direction = direction
		self.mainAxisAlignment = mainAxisAlignment
		self.crossAxisAlignment = crossAxisAlignment
		self.mainAxisSize = mainAxisSize
	def render(self):
		style = {'display': 'flex',
				'flex-direction': self.direction,
				'flex-wrap': 'nowrap',
				'justify-content':self.mainAxisAlignment,
				'align-items':self.crossAxisAlignment}
		if self.mainAxisSize==MainAxisSize.max:
			if self.direction=='row':
				style['width'] = '100%'
			else:
				style['height'] = '100%'
		e = maketag('div', style=style)
		for child in self.children:
			e.appendChild(child.render())
		return e

class Row(Flex):
	def __init__(self, children, 
			mainAxisAlignment=MainAxisAlignment.start, 
			crossAxisAlignment=CrossAxisAlignment.center, 
			mainAxisSize=MainAxisSize.min):
		super(Row, self).__init__(children,
									direction=Axis.horizontal,
									mainAxisAlignment=mainAxisAlignment,
									crossAxisAlignment=crossAxisAlignment,
									mainAxisSize=mainAxisSize)

class Column(Flex):
	def __init__(self, children, 
			mainAxisAlignment=MainAxisAlignment.start, 
			crossAxisAlignment=CrossAxisAlignment.center, 
			mainAxisSize=MainAxisSize.min):
		super(Column, self).__init__(children,
									direction=Axis.vertical,
									mainAxisAlignment=mainAxisAlignment,
									crossAxisAlignment=crossAxisAlignment,
									mainAxisSize=mainAxisSize)


# MDC (Material Design Components)

class Scaffold:
	def __init__(self, appBar, body):
		assert hasattr(appBar, 'render') and callable(appBar.render)
		assert hasattr(body, 'render') and callable(body.render)
		self.appBar = appBar
		self.body = body
	def render(self):
		scafflod_height = javascript.document.documentElement.clientHeight.data()
		e = maketag('div', style={'display':'flex', 'flex-flow':'column', 'height':f'{scafflod_height}px'})
		header = maketag('header', {'class':'mdc-top-app-bar mdc-top-app-bar--fixed'}, 
						style={'flex': '0 1 auto'})
		e.appendChild(header)
		main = maketag('header', {'class':'mdc-top-app-bar--fixed-adjust'}, 
						style={'flex': '1 1 auto'})
		e.appendChild(main)
		header.appendChild(self.appBar.render())
		main.appendChild(self.body.render())
		self.topAppBar = javascript.mdc.topAppBar.MDCTopAppBar.new(e)
		return e

class AppBar:
	def __init__(self, leading=None, title=None, actions=None):
		if leading is not None:
			assert hasattr(leading, 'render') and callable(leading.render)
		if title is not None:
			assert hasattr(title, 'render') and callable(title.render)
		if actions is not None:
			assert isinstance(actions, (tuple,list))
			for action in actions:
				assert hasattr(action, 'render') and callable(action.render)
		self.leading = leading
		self.title = title
		self.actions = actions
	def render(self):
		e = maketag('div', {'class':'mdc-top-app-bar__row'})
		start = maketag('header', {'class':'mdc-top-app-bar__section mdc-top-app-bar__section--align-start'})
		e.appendChild(start)
		end = maketag('header', {'class':'mdc-top-app-bar__section mdc-top-app-bar__section--align-end', 'rule':'toolbar'})
		e.appendChild(end)
		if self.leading is not None:
			leading = self.leading.render()
			add_class(leading, 'mdc-top-app-bar__navigation-icon')
			start.appendChild(leading)
		if self.title is not None:
			title = self.title.render()
			add_class(title, 'mdc-top-app-bar__title')
			start.appendChild(title)
		if self.actions is not None:
			for action in self.actions:
				action = action.render()
				add_class(action, 'mdc-top-app-bar__action-item')
				end.appendChild(action)
		return e

class IconButton:
	def __init__(self, icon, onPressed=None):
		assert isinstance(icon, Icon)
		if onPressed is not None:
			assert callable(onPressed)
		self.icon = icon
		self.onPressed = onPressed
	def render(self):
		e = maketag('button', {'class':'material-icons mdc-top-app-bar__action-item mdc-icon-button'})
		e.appendChild(javascript.document.createTextNode(self.icon.icon))
		if self.onPressed is not None:
			e.bind('click', self.onPressed)
		javascript.mdc.ripple.MDCRipple.new(e)
		return e

class Icon:
	def __init__(self, icon):
		assert isinstance(icon, str)
		self.icon = icon
	def render(self):
		e = maketag('span', {'class':'material-icons'})
		e.appendChild(javascript.document.createTextNode(self.icon))	
		return e

from . import flutter_icons
Icons = flutter_icons.Icons


# TODO
# https://material.io/components/banners
# https://material.io/components/buttons
# https://material.io/components/cards
# https://material.io/components/checkboxes
# https://material.io/components/chips
# https://material.io/components/data-tables
# https://material.io/components/dialogs
# https://material.io/components/lists
# https://material.io/components/menus
# https://material.io/components/navigation-drawer
# https://material.io/components/radio-buttons
# https://material.io/components/sheets-side
# https://material.io/components/snackbars
# https://material.io/components/switches
# https://material.io/components/tabs
# https://material.io/components/text-fields
# https://material.io/components/tooltips
